diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index f2e5f4e3c9..a0abebc5fe 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -136,7 +136,7 @@ def add_pipeline_options(parser, plumber): [ 'base_font_size', 'disable_font_rescaling', 'font_size_mapping', 'embed_font_family', - 'subset_embedded_fonts', + 'subset_embedded_fonts', 'embed_all_fonts', 'line_height', 'minimum_line_height', 'linearize_tables', 'extra_css', 'filter_css', @@ -320,7 +320,7 @@ def main(args=sys.argv): opts.search_replace = read_sr_patterns(opts.search_replace, log) recommendations = [(n.dest, getattr(opts, n.dest), - OptionRecommendation.HIGH) \ + OptionRecommendation.HIGH) for n in parser.options_iter() if n.dest] plumber.merge_ui_recommendations(recommendations) @@ -342,3 +342,4 @@ def main(args=sys.argv): if __name__ == '__main__': sys.exit(main()) + diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 1f459229c8..a96574e904 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -205,6 +205,16 @@ OptionRecommendation(name='embed_font_family', 'with some output formats, principally EPUB and AZW3.') ), +OptionRecommendation(name='embed_all_fonts', + recommended_value=False, level=OptionRecommendation.LOW, + help=_( + 'Embed every font that is referenced in the input document ' + 'but not already embedded. This will search your system for the ' + 'fonts, and if found, they will be embedded. Embedding will only work ' + 'if the format you are converting to supports embedded fonts, such as ' + 'EPUB, AZW3 or PDF.' + )), + OptionRecommendation(name='subset_embedded_fonts', recommended_value=False, level=OptionRecommendation.LOW, help=_( @@ -965,6 +975,9 @@ OptionRecommendation(name='search_replace', if self.for_regex_wizard and hasattr(self.opts, 'no_process'): self.opts.no_process = True self.flush() + if self.opts.embed_all_fonts or self.opts.embed_font_family: + # Start the threaded font scanner now, for performance + from calibre.utils.fonts.scanner import font_scanner # noqa import cssutils, logging cssutils.log.setLevel(logging.WARN) get_types_map() # Ensure the mimetypes module is intialized @@ -1129,6 +1142,10 @@ OptionRecommendation(name='search_replace', RemoveFakeMargins()(self.oeb, self.log, self.opts) RemoveAdobeMargins()(self.oeb, self.log, self.opts) + if self.opts.embed_all_fonts: + from calibre.ebooks.oeb.transforms.embed_fonts import EmbedFonts + EmbedFonts()(self.oeb, self.log, self.opts) + if self.opts.subset_embedded_fonts and self.output_plugin.file_type != 'pdf': from calibre.ebooks.oeb.transforms.subset import SubsetFonts SubsetFonts()(self.oeb, self.log, self.opts) diff --git a/src/calibre/ebooks/oeb/transforms/embed_fonts.py b/src/calibre/ebooks/oeb/transforms/embed_fonts.py new file mode 100644 index 0000000000..027b8af1de --- /dev/null +++ b/src/calibre/ebooks/oeb/transforms/embed_fonts.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' + +import logging +from collections import defaultdict + +import cssutils +from lxml import etree + +from calibre import guess_type +from calibre.ebooks.oeb.base import XPath, CSS_MIME, XHTML +from calibre.ebooks.oeb.transforms.subset import get_font_properties, find_font_face_rules, elem_style +from calibre.utils.filenames import ascii_filename +from calibre.utils.fonts.scanner import font_scanner, NoFonts + +def used_font(style, embedded_fonts): + ff = [unicode(f) for f in style.get('font-family', []) if unicode(f).lower() not in { + 'serif', 'sansserif', 'sans-serif', 'fantasy', 'cursive', 'monospace'}] + if not ff: + return False, None + lnames = {unicode(x).lower() for x in ff} + + matching_set = [] + + # Filter on font-family + for ef in embedded_fonts: + flnames = {x.lower() for x in ef.get('font-family', [])} + if not lnames.intersection(flnames): + continue + matching_set.append(ef) + if not matching_set: + return True, None + + # Filter on font-stretch + widths = {x:i for i, x in enumerate(('ultra-condensed', + 'extra-condensed', 'condensed', 'semi-condensed', 'normal', + 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' + ))} + + width = widths[style.get('font-stretch', 'normal')] + for f in matching_set: + f['width'] = widths[style.get('font-stretch', 'normal')] + + min_dist = min(abs(width-f['width']) for f in matching_set) + if min_dist > 0: + return True, None + nearest = [f for f in matching_set if abs(width-f['width']) == + min_dist] + if width <= 4: + lmatches = [f for f in nearest if f['width'] <= width] + else: + lmatches = [f for f in nearest if f['width'] >= width] + matching_set = (lmatches or nearest) + + # Filter on font-style + fs = style.get('font-style', 'normal') + matching_set = [f for f in matching_set if f.get('font-style', 'normal') == fs] + + # Filter on font weight + fw = int(style.get('font-weight', '400')) + matching_set = [f for f in matching_set if f.get('weight', 400) == fw] + + if not matching_set: + return True, None + return True, matching_set[0] + + +class EmbedFonts(object): + + ''' + Embed all referenced fonts, if found on system. Must be called after CSS flattening. + ''' + + def __call__(self, oeb, log, opts): + self.oeb, self.log, self.opts = oeb, log, opts + self.sheet_cache = {} + self.find_style_rules() + self.find_embedded_fonts() + self.parser = cssutils.CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css')) + self.warned = set() + self.warned2 = set() + + for item in oeb.spine: + if not hasattr(item.data, 'xpath'): + continue + sheets = [] + for href in XPath('//h:link[@href and @type="text/css"]/@href')(item.data): + sheet = self.oeb.manifest.hrefs.get(item.abshref(href), None) + if sheet is not None: + sheets.append(sheet) + if sheets: + self.process_item(item, sheets) + + def find_embedded_fonts(self): + ''' + Find all @font-face rules and extract the relevant info from them. + ''' + self.embedded_fonts = [] + for item in self.oeb.manifest: + if not hasattr(item.data, 'cssRules'): + continue + self.embedded_fonts.extend(find_font_face_rules(item, self.oeb)) + + def find_style_rules(self): + ''' + Extract all font related style information from all stylesheets into a + dict mapping classes to font properties specified by that class. All + the heavy lifting has already been done by the CSS flattening code. + ''' + rules = defaultdict(dict) + for item in self.oeb.manifest: + if not hasattr(item.data, 'cssRules'): + continue + for i, rule in enumerate(item.data.cssRules): + if rule.type != rule.STYLE_RULE: + continue + props = {k:v for k,v in + get_font_properties(rule).iteritems() if v} + if not props: + continue + for sel in rule.selectorList: + sel = sel.selectorText + if sel and sel.startswith('.'): + # We dont care about pseudo-selectors as the worst that + # can happen is some extra characters will remain in + # the font + sel = sel.partition(':')[0] + rules[sel[1:]].update(props) + + self.style_rules = dict(rules) + + def get_page_sheet(self): + if self.page_sheet is None: + manifest = self.oeb.manifest + id_, href = manifest.generate('page_css', 'page_styles.css') + self.page_sheet = manifest.add(id_, href, CSS_MIME, data=self.parser.parseString('', validate=False)) + head = self.current_item.xpath('//*[local-name()="head"][1]') + if head: + href = self.current_item.relhref(href) + l = etree.SubElement(head[0], XHTML('link'), + rel='stylesheet', type=CSS_MIME, href=href) + l.tail = '\n' + else: + self.log.warn('No cannot embed font rules') + return self.page_sheet + + def process_item(self, item, sheets): + ff_rules = [] + self.current_item = item + self.page_sheet = None + for sheet in sheets: + if 'page_css' in sheet.id: + ff_rules.extend(find_font_face_rules(sheet, self.oeb)) + self.page_sheet = sheet + + base = {'font-family':['serif'], 'font-weight': '400', + 'font-style':'normal', 'font-stretch':'normal'} + + for body in item.data.xpath('//*[local-name()="body"]'): + self.find_usage_in(body, base, ff_rules) + + def find_usage_in(self, elem, inherited_style, ff_rules): + style = elem_style(self.style_rules, elem.get('class', '') or '', inherited_style) + for child in elem: + self.find_usage_in(child, style, ff_rules) + has_font, existing = used_font(style, ff_rules) + if not has_font: + return + if existing is None: + in_book = used_font(style, self.embedded_fonts)[1] + if in_book is None: + # Try to find the font in the system + added = self.embed_font(style) + if added is not None: + ff_rules.append(added) + self.embedded_fonts.append(added) + else: + # TODO: Create a page rule from the book rule (cannot use it + # directly as paths might be different) + item = in_book['item'] + sheet = self.parser.parseString(in_book['rule'].cssText, validate=False) + rule = sheet.cssRules[0] + page_sheet = self.get_page_sheet() + href = page_sheet.abshref(item.href) + rule.style.setProperty('src', 'url(%s)' % href) + ff_rules.append(find_font_face_rules(sheet, self.oeb)[0]) + page_sheet.data.insertRule(rule, len(page_sheet.data.cssRules)) + + def embed_font(self, style): + ff = [unicode(f) for f in style.get('font-family', []) if unicode(f).lower() not in { + 'serif', 'sansserif', 'sans-serif', 'fantasy', 'cursive', 'monospace'}] + if not ff: + return + ff = ff[0] + if ff in self.warned: + return + try: + fonts = font_scanner.fonts_for_family(ff) + except NoFonts: + self.log.warn('Failed to find fonts for family:', ff, 'not embedding') + self.warned.add(ff) + return + try: + weight = int(style.get('font-weight', '400')) + except (ValueError, TypeError, AttributeError): + w = style['font-weight'] + if w not in self.warned2: + self.log.warn('Invalid weight in font style: %r' % w) + self.warned2.add(w) + return + for f in fonts: + if f['weight'] == weight and f['font-style'] == style.get('font-style', 'normal') and f['font-stretch'] == style.get('font-stretch', 'normal'): + self.log('Embedding font %s from %s' % (f['full_name'], f['path'])) + data = font_scanner.get_font_data(f) + name = f['full_name'] + ext = 'otf' if f['is_otf'] else 'ttf' + name = ascii_filename(name).replace(' ', '-').replace('(', '').replace(')', '') + fid, href = self.oeb.manifest.generate(id=u'font', href=u'fonts/%s.%s'%(name, ext)) + item = self.oeb.manifest.add(fid, href, guess_type('dummy.'+ext)[0], data=data) + item.unload_data_from_memory() + page_sheet = self.get_page_sheet() + href = page_sheet.relhref(item.href) + css = '''@font-face { font-family: "%s"; font-weight: %s; font-style: %s; font-stretch: %s; src: url(%s) }''' % ( + f['font-family'], f['font-weight'], f['font-style'], f['font-stretch'], href) + sheet = self.parser.parseString(css, validate=False) + page_sheet.data.insertRule(sheet.cssRules[0], len(page_sheet.data.cssRules)) + return find_font_face_rules(sheet, self.oeb)[0] + diff --git a/src/calibre/ebooks/oeb/transforms/flatcss.py b/src/calibre/ebooks/oeb/transforms/flatcss.py index dd2d20333d..9c08934938 100644 --- a/src/calibre/ebooks/oeb/transforms/flatcss.py +++ b/src/calibre/ebooks/oeb/transforms/flatcss.py @@ -194,7 +194,7 @@ class CSSFlattener(object): for i, font in enumerate(faces): ext = 'otf' if font['is_otf'] else 'ttf' fid, href = self.oeb.manifest.generate(id=u'font', - href=u'%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext)) + href=u'fonts/%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext)) item = self.oeb.manifest.add(fid, href, guess_type('dummy.'+ext)[0], data=font_scanner.get_font_data(font)) diff --git a/src/calibre/ebooks/oeb/transforms/subset.py b/src/calibre/ebooks/oeb/transforms/subset.py index 744e37b193..96170bd49c 100644 --- a/src/calibre/ebooks/oeb/transforms/subset.py +++ b/src/calibre/ebooks/oeb/transforms/subset.py @@ -12,6 +12,111 @@ from collections import defaultdict from calibre.ebooks.oeb.base import urlnormalize from calibre.utils.fonts.sfnt.subset import subset, NoGlyphs, UnsupportedFont +def get_font_properties(rule, default=None): + ''' + Given a CSS rule, extract normalized font properties from + it. Note that shorthand font property should already have been expanded + by the CSS flattening code. + ''' + props = {} + s = rule.style + for q in ('font-family', 'src', 'font-weight', 'font-stretch', + 'font-style'): + g = 'uri' if q == 'src' else 'value' + try: + val = s.getProperty(q).propertyValue[0] + val = getattr(val, g) + if q == 'font-family': + val = [x.value for x in s.getProperty(q).propertyValue] + if val and val[0] == 'inherit': + val = None + except (IndexError, KeyError, AttributeError, TypeError, ValueError): + val = None if q in {'src', 'font-family'} else default + if q in {'font-weight', 'font-stretch', 'font-style'}: + val = unicode(val).lower() if (val or val == 0) else val + if val == 'inherit': + val = default + if q == 'font-weight': + val = {'normal':'400', 'bold':'700'}.get(val, val) + if val not in {'100', '200', '300', '400', '500', '600', '700', + '800', '900', 'bolder', 'lighter'}: + val = default + if val == 'normal': + val = '400' + elif q == 'font-style': + if val not in {'normal', 'italic', 'oblique'}: + val = default + elif q == 'font-stretch': + if val not in {'normal', 'ultra-condensed', 'extra-condensed', + 'condensed', 'semi-condensed', 'semi-expanded', + 'expanded', 'extra-expanded', 'ultra-expanded'}: + val = default + props[q] = val + return props + + +def find_font_face_rules(sheet, oeb): + ''' + Find all @font-face rules in the given sheet and extract the relevant info from them. + sheet can be either a ManifestItem or a CSSStyleSheet. + ''' + ans = [] + try: + rules = sheet.data.cssRules + except AttributeError: + rules = sheet.cssRules + + for i, rule in enumerate(rules): + if rule.type != rule.FONT_FACE_RULE: + continue + props = get_font_properties(rule, default='normal') + if not props['font-family'] or not props['src']: + continue + + try: + path = sheet.abshref(props['src']) + except AttributeError: + path = props['src'] + ff = oeb.manifest.hrefs.get(urlnormalize(path), None) + if not ff: + continue + props['item'] = ff + if props['font-weight'] in {'bolder', 'lighter'}: + props['font-weight'] = '400' + props['weight'] = int(props['font-weight']) + props['rule'] = rule + props['chars'] = set() + ans.append(props) + + return ans + + +def elem_style(style_rules, cls, inherited_style): + ''' + Find the effective style for the given element. + ''' + classes = cls.split() + style = inherited_style.copy() + for cls in classes: + style.update(style_rules.get(cls, {})) + wt = style.get('font-weight', None) + pwt = inherited_style.get('font-weight', '400') + if wt == 'bolder': + style['font-weight'] = { + '100':'400', + '200':'400', + '300':'400', + '400':'700', + '500':'700', + }.get(pwt, '900') + elif wt == 'lighter': + style['font-weight'] = { + '600':'400', '700':'400', + '800':'700', '900':'700'}.get(pwt, '100') + + return style + + class SubsetFonts(object): ''' @@ -76,72 +181,15 @@ class SubsetFonts(object): self.log('Reduced total font size to %.1f%% of original'% (totals[0]/totals[1] * 100)) - def get_font_properties(self, rule, default=None): - ''' - Given a CSS rule, extract normalized font properties from - it. Note that shorthand font property should already have been expanded - by the CSS flattening code. - ''' - props = {} - s = rule.style - for q in ('font-family', 'src', 'font-weight', 'font-stretch', - 'font-style'): - g = 'uri' if q == 'src' else 'value' - try: - val = s.getProperty(q).propertyValue[0] - val = getattr(val, g) - if q == 'font-family': - val = [x.value for x in s.getProperty(q).propertyValue] - if val and val[0] == 'inherit': - val = None - except (IndexError, KeyError, AttributeError, TypeError, ValueError): - val = None if q in {'src', 'font-family'} else default - if q in {'font-weight', 'font-stretch', 'font-style'}: - val = unicode(val).lower() if (val or val == 0) else val - if val == 'inherit': - val = default - if q == 'font-weight': - val = {'normal':'400', 'bold':'700'}.get(val, val) - if val not in {'100', '200', '300', '400', '500', '600', '700', - '800', '900', 'bolder', 'lighter'}: - val = default - if val == 'normal': val = '400' - elif q == 'font-style': - if val not in {'normal', 'italic', 'oblique'}: - val = default - elif q == 'font-stretch': - if val not in { 'normal', 'ultra-condensed', 'extra-condensed', - 'condensed', 'semi-condensed', 'semi-expanded', - 'expanded', 'extra-expanded', 'ultra-expanded'}: - val = default - props[q] = val - return props - def find_embedded_fonts(self): ''' Find all @font-face rules and extract the relevant info from them. ''' self.embedded_fonts = [] for item in self.oeb.manifest: - if not hasattr(item.data, 'cssRules'): continue - for i, rule in enumerate(item.data.cssRules): - if rule.type != rule.FONT_FACE_RULE: - continue - props = self.get_font_properties(rule, default='normal') - if not props['font-family'] or not props['src']: - continue - - path = item.abshref(props['src']) - ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None) - if not ff: - continue - props['item'] = ff - if props['font-weight'] in {'bolder', 'lighter'}: - props['font-weight'] = '400' - props['weight'] = int(props['font-weight']) - props['chars'] = set() - props['rule'] = rule - self.embedded_fonts.append(props) + if not hasattr(item.data, 'cssRules'): + continue + self.embedded_fonts.extend(find_font_face_rules(item, self.oeb)) def find_style_rules(self): ''' @@ -151,12 +199,13 @@ class SubsetFonts(object): ''' rules = defaultdict(dict) for item in self.oeb.manifest: - if not hasattr(item.data, 'cssRules'): continue + if not hasattr(item.data, 'cssRules'): + continue for i, rule in enumerate(item.data.cssRules): if rule.type != rule.STYLE_RULE: continue props = {k:v for k,v in - self.get_font_properties(rule).iteritems() if v} + get_font_properties(rule).iteritems() if v} if not props: continue for sel in rule.selectorList: @@ -172,41 +221,17 @@ class SubsetFonts(object): def find_font_usage(self): for item in self.oeb.manifest: - if not hasattr(item.data, 'xpath'): continue + if not hasattr(item.data, 'xpath'): + continue for body in item.data.xpath('//*[local-name()="body"]'): base = {'font-family':['serif'], 'font-weight': '400', 'font-style':'normal', 'font-stretch':'normal'} self.find_usage_in(body, base) - def elem_style(self, cls, inherited_style): - ''' - Find the effective style for the given element. - ''' - classes = cls.split() - style = inherited_style.copy() - for cls in classes: - style.update(self.style_rules.get(cls, {})) - wt = style.get('font-weight', None) - pwt = inherited_style.get('font-weight', '400') - if wt == 'bolder': - style['font-weight'] = { - '100':'400', - '200':'400', - '300':'400', - '400':'700', - '500':'700', - }.get(pwt, '900') - elif wt == 'lighter': - style['font-weight'] = { - '600':'400', '700':'400', - '800':'700', '900':'700'}.get(pwt, '100') - - return style - def used_font(self, style): ''' Given a style find the embedded font that matches it. Returns None if - no match is found ( can happen if not family matches). + no match is found (can happen if no family matches). ''' ff = style.get('font-family', []) lnames = {unicode(x).lower() for x in ff} @@ -222,7 +247,7 @@ class SubsetFonts(object): return None # Filter on font-stretch - widths = {x:i for i, x in enumerate(( 'ultra-condensed', + widths = {x:i for i, x in enumerate(('ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' ))} @@ -280,7 +305,7 @@ class SubsetFonts(object): return ans def find_usage_in(self, elem, inherited_style): - style = self.elem_style(elem.get('class', '') or '', inherited_style) + style = elem_style(self.style_rules, elem.get('class', '') or '', inherited_style) for child in elem: self.find_usage_in(child, style) font = self.used_font(style) @@ -290,3 +315,4 @@ class SubsetFonts(object): font['chars'] |= chars + diff --git a/src/calibre/gui2/convert/look_and_feel.py b/src/calibre/gui2/convert/look_and_feel.py index 24ee288cc6..a3e364b9ca 100644 --- a/src/calibre/gui2/convert/look_and_feel.py +++ b/src/calibre/gui2/convert/look_and_feel.py @@ -32,7 +32,7 @@ class LookAndFeelWidget(Widget, Ui_Form): Widget.__init__(self, parent, ['change_justification', 'extra_css', 'base_font_size', 'font_size_mapping', 'line_height', 'minimum_line_height', - 'embed_font_family', 'subset_embedded_fonts', + 'embed_font_family', 'embed_all_fonts', 'subset_embedded_fonts', 'smarten_punctuation', 'unsmarten_punctuation', 'disable_font_rescaling', 'insert_blank_line', 'remove_paragraph_spacing', diff --git a/src/calibre/gui2/convert/look_and_feel.ui b/src/calibre/gui2/convert/look_and_feel.ui index 43736fb1f2..e9d9caeed7 100644 --- a/src/calibre/gui2/convert/look_and_feel.ui +++ b/src/calibre/gui2/convert/look_and_feel.ui @@ -14,6 +14,70 @@ Form + + + + Keep &ligatures + + + + + + + &Linearize tables + + + + + + + Base &font size: + + + opt_base_font_size + + + + + + + &Line size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + opt_insert_blank_line_size + + + + + + + true + + + + + + + Remove &spacing between paragraphs + + + + + + + &Indent size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + opt_remove_paragraph_spacing_indent_size + + + @@ -24,6 +88,57 @@ + + + + Insert &blank line between paragraphs + + + + + + + em + + + 1 + + + + + + + Text &justification: + + + opt_change_justification + + + + + + + + + + Smarten &punctuation + + + + + + + &Transliterate unicode characters to ASCII + + + + + + + &UnSmarten punctuation + + + @@ -44,51 +159,6 @@ - - - - % - - - 1 - - - 900.000000000000000 - - - - - - - pt - - - 1 - - - 0.000000000000000 - - - 50.000000000000000 - - - 1.000000000000000 - - - 15.000000000000000 - - - - - - - Font size &key: - - - opt_font_size_mapping - - - @@ -133,56 +203,72 @@ - - - - true - - - - - - - Remove &spacing between paragraphs - - - - - - - &Indent size: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - opt_remove_paragraph_spacing_indent_size - - - - - - - <p>When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent. - - - No change - + + - em + % + + + 1 + + + 900.000000000000000 + + + + + + + pt 1 - -0.100000000000000 + 0.000000000000000 + + + 50.000000000000000 - 0.100000000000000 + 1.000000000000000 + + + 15.000000000000000 - + + + + &Disable font size rescaling + + + + + + + + + + Font size &key: + + + opt_font_size_mapping + + + + + + + &Embed font family: + + + opt_embed_font_family + + + + 0 @@ -300,121 +386,42 @@ - - - - Insert &blank line between paragraphs - - - - + + + <p>When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent. + + + No change + em 1 - - - - - - Text &justification: + + -0.100000000000000 - - opt_change_justification + + 0.100000000000000 - - - - - - - Smarten &punctuation - - - - - - - &Transliterate unicode characters to ASCII - - - - - - - &UnSmarten punctuation - - - - - - - Keep &ligatures - - - - - - - &Linearize tables - - - - - - - Base &font size: - - - opt_base_font_size - - - - - - - &Line size: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - opt_insert_blank_line_size - - - - - - - &Embed font family: - - - opt_embed_font_family - - - - - - - &Disable font size rescaling - - - - - - - + &Subset all embedded fonts + + + + &Embed referenced fonts + + +