feat: add options to adjust paragraph margin, word spacing, letter spacing, text indent and font weight (#310)

This commit is contained in:
Huang Xin
2025-02-07 18:23:12 +01:00
committed by GitHub
parent adfdb10d8e
commit dee1e28a61
24 changed files with 271 additions and 49 deletions
@@ -8,8 +8,8 @@ import { useDefaultIconSize, useResponsiveSize } from '@/hooks/useResponsiveSize
interface DropdownProps {
family?: string;
selected: string;
options: { option: string; label: string }[];
moreOptions?: string[];
options: { option: string; label?: string }[];
moreOptions?: { option: string; label?: string }[];
onSelect: (option: string) => void;
onGetFontFamily: (option: string, family: string) => string;
}
@@ -25,7 +25,8 @@ const FontDropdown: React.FC<DropdownProps> = ({
const _ = useTranslation();
const iconSize16 = useResponsiveSize(16);
const defaultIconSize = useDefaultIconSize();
const selectedOption = options.find((option) => option.option === selected) || options[0]!;
const allOptions = [...options, ...(moreOptions ?? [])];
const selectedOption = allOptions.find((option) => option.option === selected) ?? allOptions[0]!;
return (
<div className='dropdown dropdown-top'>
<button
@@ -68,14 +69,14 @@ const FontDropdown: React.FC<DropdownProps> = ({
'!mr-5 mb-[-46px] inline max-h-80 w-[200px] overflow-y-scroll',
)}
>
{moreOptions.map((option) => (
<li key={option} onClick={() => onSelect(option)}>
{moreOptions.map((option, index) => (
<li key={`${index}-${option.option}`} onClick={() => onSelect(option.option)}>
<div className='flex items-center px-2'>
<span style={{ minWidth: `${defaultIconSize}px` }}>
{selected === option && <MdCheck className='text-base-content' />}
{selected === option.option && <MdCheck className='text-base-content' />}
</span>
<span style={{ fontFamily: onGetFontFamily(option, family ?? '') }}>
{option}
<span style={{ fontFamily: onGetFontFamily(option.option, family ?? '') }}>
{option.label || option.option}
</span>
</div>
</li>
@@ -50,7 +50,7 @@ const FontFace = ({
<FontDropdown
family={family}
options={options.map((option) => ({ option, label: option }))}
moreOptions={moreOptions}
moreOptions={moreOptions?.map((option) => ({ option, label: option })) ?? []}
selected={selected}
onSelect={onSelect}
onGetFontFamily={handleFontFaceFont}
@@ -106,6 +106,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const [serifFont, setSerifFont] = useState(viewSettings.serifFont!);
const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!);
const [monospaceFont, setMonospaceFont] = useState(viewSettings.monospaceFont!);
const [fontWeight, setFontWeight] = useState(viewSettings.fontWeight!);
useEffect(() => {
if (isTauriAppPlatform() && FONT_ENUM_SUPPORTED_OS_PLATFORMS.includes(osPlatform)) {
@@ -149,6 +150,17 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minFontSize]);
useEffect(() => {
viewSettings.fontWeight = fontWeight;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.fontWeight = fontWeight;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fontWeight]);
useEffect(() => {
viewSettings.serifFont = serifFont;
setViewSettings(bookKey, viewSettings);
@@ -232,6 +244,23 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Font Weight')}</h2>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label={_('Font Weight')}
value={fontWeight}
onChange={setFontWeight}
min={100}
max={900}
step={100}
/>
</div>
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Font Family')}</h2>
<div className='card border-base-200 border shadow'>
@@ -22,7 +22,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [paragraphMargin, setParagraphMargin] = useState(viewSettings.paragraphMargin!);
const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!);
const [wordSpacing, setWordSpacing] = useState(viewSettings.wordSpacing!);
const [letterSpacing, setLetterSpacing] = useState(viewSettings.letterSpacing!);
const [textIndent, setTextIndent] = useState(viewSettings.textIndent!);
const [fullJustification, setFullJustification] = useState(viewSettings.fullJustification!);
const [hyphenation, setHyphenation] = useState(viewSettings.hyphenation!);
const [marginPx, setMarginPx] = useState(viewSettings.marginPx!);
@@ -32,6 +36,17 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const [maxBlockSize, setMaxBlockSize] = useState(viewSettings.maxBlockSize!);
const [writingMode, setWritingMode] = useState(viewSettings.writingMode!);
useEffect(() => {
viewSettings.paragraphMargin = paragraphMargin;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.paragraphMargin = paragraphMargin;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [paragraphMargin]);
useEffect(() => {
viewSettings.lineHeight = lineHeight;
setViewSettings(bookKey, viewSettings);
@@ -43,6 +58,39 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lineHeight]);
useEffect(() => {
viewSettings.wordSpacing = wordSpacing;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.wordSpacing = wordSpacing;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wordSpacing]);
useEffect(() => {
viewSettings.letterSpacing = letterSpacing;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.letterSpacing = letterSpacing;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [letterSpacing]);
useEffect(() => {
viewSettings.textIndent = textIndent;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.textIndent = textIndent;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [textIndent]);
useEffect(() => {
viewSettings.fullJustification = fullJustification;
setViewSettings(bookKey, viewSettings);
@@ -188,13 +236,49 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label={_('Line Height')}
label={_('Paragraph Margin')}
value={paragraphMargin}
onChange={setParagraphMargin}
min={0}
max={4}
step={0.5}
/>
<NumberInput
className='config-item-top'
label={_('Line Spacing')}
value={lineHeight}
onChange={setLineHeight}
min={1.0}
max={3.0}
step={0.1}
/>
<NumberInput
className='config-item-top'
label={_('Word Spacing')}
value={wordSpacing}
onChange={setWordSpacing}
min={-4}
max={8}
step={0.5}
/>
<NumberInput
className='config-item-top'
label={_('Letter Spacing')}
value={letterSpacing}
onChange={setLetterSpacing}
min={-2}
max={4}
step={0.1}
/>
<NumberInput
className='config-item-top'
label={_('Text Indent')}
value={textIndent}
onChange={setTextIndent}
min={-2}
max={4}
step={1}
/>
<div className='config-item config-item-bottom'>
<span className=''>{_('Full Justification')}</span>
<input