public HtmlToEpubConverter( Counter counter, ILogWriter log, IOptionProviderInputFile options, MecabParser parser, MecabReader reader, MecabBackend backend, XHtmlMaker xhtmlMaker, JmdicFastReader dicReader, ContentsBreaker breaker, EpubMaker epubMaker, SentenceBreaker sentenceBreaker) { _inputFile = options.InputFile; _counter = counter; _log = log; _parser = parser; _reader = reader; _mecabBackend = backend; _xhtmlMaker = xhtmlMaker; _dicReader = dicReader; _breaker = breaker; _epubMaker = epubMaker; _sentenceBreaker = sentenceBreaker; }
/// <summary> /// Creates an ePub file at the location specified by parameters /// </summary> /// <param name="parameters">BookPath and epubOutputPath should be set.</param> /// <param name="control">The epub code needs a control that goes back to the main thread, in order to run some tasks that need to be on the main thread</param> public static void CreateEpubArtifact(CreateArtifactsParameters parameters, Control control) { if (String.IsNullOrEmpty(parameters.EpubOutputPath)) { return; } string directoryName = Path.GetDirectoryName(parameters.EpubOutputPath); Directory.CreateDirectory(directoryName); // Ensures that the directory exists BookServer bookServer = _projectContext.BookServer; BookThumbNailer thumbNailer = _projectContext.ThumbNailer; var maker = new EpubMaker(thumbNailer, bookServer); maker.ControlForInvoke = control; maker.Book = _book; maker.Unpaginated = true; // so far they all are maker.OneAudioPerPage = true; // default used in EpubApi // Enhance: maybe we want book to have image descriptions on page? use reader font sizes? // Make the epub maker.SaveEpub(parameters.EpubOutputPath, new NullWebSocketProgress()); }
internal void SaveAsEpub() { lock (_epubMakerLock) { if (_pendingSaveAsPath == null) { return; } EpubMaker.ZipAndSaveEpub(_pendingSaveAsPath, _progress); _pendingSaveAsPath = null; } }
internal void PrepareToStageEpub() { if (EpubMaker != null) { //it has state that we don't want to reuse, so make a new one EpubMaker.Dispose(); EpubMaker = null; } EpubMaker = new EpubMaker(_thumbNailer, _isoloator); EpubMaker.Book = BookSelection.CurrentSelection; EpubMaker.Unpaginated = true; // Enhance: UI? }
public static int Handle(GetUsedFontsParameters options) { if (!Directory.Exists(options.BookPath)) { if (options.BookPath.Contains(".htm")) { Debug.WriteLine("Supply only the directory, not the path to the file."); Console.Error.WriteLine("Supply only the directory, not the path to the file."); } else { Debug.WriteLine("Could not find " + options.BookPath); Console.Error.WriteLine("Could not find " + options.BookPath); } return(1); } Console.WriteLine("Gathering font data."); // Some of this might be useful if we end up needing to instantiate the book to figure out what // is REALLY needed (as opposed to just mentioned in a style sheet). //var collectionFolder = Path.GetDirectoryName(options.BookPath); //var projectSettingsPath = Directory.GetFiles(collectionFolder, "*.bloomCollection").FirstOrDefault(); //var collectionSettings = new CollectionSettings(projectSettingsPath); //XMatterPackFinder xmatterFinder = new XMatterPackFinder(new[] {BloomFileLocator.GetInstalledXMatterDirectory()}); //var locator = new BloomFileLocator(collectionSettings, xmatterFinder, ProjectContext.GetFactoryFileLocations(), // ProjectContext.GetFoundFileLocations(), ProjectContext.GetAfterXMatterFileLocations()); //var bookInfo = new BookInfo(options.BookPath, true); //var book = new Book.Book(bookInfo, new BookStorage(options.BookPath, locator, new BookRenamedEvent(), collectionSettings), // null, collectionSettings, null, null, new BookRefreshEvent()); //book.BringBookUpToDate(new NullProgress()); var fonts = EpubMaker.GetFontsUsed(options.BookPath, false).ToList(); fonts.Sort(); Directory.CreateDirectory(Path.GetDirectoryName(options.ReportPath)); using (var report = new StreamWriter(options.ReportPath)) { foreach (var font in fonts) { report.WriteLine(font); } } Console.WriteLine("Finished gathering font data."); Debug.WriteLine("Finished gathering font data."); return(0); }
public void UpdateAndSave(EpubPublishUiSettings newSettings, string path, bool force, WebSocketProgress progress = null) { bool succeeded; do { lock (this) { succeeded = UpdatePreview(newSettings, force, progress); if (succeeded) { EpubMaker.SaveEpub(path, _progress); _webSocketServer.SendString(kWebsocketContext, kWebsocketEventId_epubReady, _previewSrc); } } } while (!succeeded && !EpubMaker.AbortRequested); // try until we get a complete epub, not interrupted by user changing something. }
internal void PrepareToStageEpub() { lock (_epubMakerLock) { if (EpubMaker != null) { //it has state that we don't want to reuse, so make a new one EpubMaker.Dispose(); EpubMaker = null; } EpubMaker = new EpubMaker(_thumbNailer, _bookServer); } EpubMaker.Book = _bookSelection.CurrentSelection; EpubMaker.Unpaginated = true; // Enhance: UI? EpubMaker.OneAudioPerPage = true; }
public string SetupEpubControlContent() { // This gets called on a background thread but one step needs to happen on the UI thread, // so the Maker needs a control to Invoke on. An Api class doesn't naturally have one to give it, // so we arrange that this class is given the Bloom main window by the PublishView when the preview // window first comes up. In production, this is roughly equivalent to just using // Form.ActiveForms.Last(), but that fails when debugging; this is more robust. EpubMaker.ControlForInvoke = ControlForInvoke; EpubMaker.StageEpub(_progress); if (StagingDirectory == null) { return(null); // aborted, hopefully already reported. } var fileLocator = _bookSelection.CurrentSelection.GetFileLocator(); var root = fileLocator.LocateDirectoryWithThrow("Readium"); var tempFolder = Path.GetDirectoryName(StagingDirectory); // This is kludge. I hope it can be improved. To make a preview we currently need all the Readium // files in a folder that is a parent of the staging folder containing the book content. // This allows us to tell Readium about the book by passing the name of the folder using the ?ePUB= // URL parameter. It doesn't work to use the original Readium file and make the parameter a full path. // It's possible that there is some variation that would work, e.g., make the param a full file:/// url // to the book folder. It's also possible we could get away with only copying the HTML file itself, // if we modified it to have localhost: links to the JS and CSS. Haven't tried this yet. The current // approach at least works. // Note October 2018: upgraded to Readium 0.3.2. Doc indicates this definitely should support // using any url (that doesn't involve a cross-domain problem) in the ?epub= param. // So we could probably now rewrite this to not copy the Readium files over. Not sure it's worth the effort. DirectoryUtilities.CopyDirectoryContents(root, tempFolder); // Not sure if we will need this. The current UI does not appear to have a way to indicate whether // we have a talking book, a book without audio, or one that has audio but it is not being published. //var audioSituationClass = "noAudioAvailable"; //if (EpubMaker.PublishWithoutAudio) // audioSituationClass = "haveAudioButNotMakingTalkingBook"; //else if (BookHasAudio) // audioSituationClass = "isTalkingBook"; var targetFile = Path.Combine(tempFolder, "index.html"); var iframeSource = targetFile.ToLocalhost() + "?epub=" + Path.GetFileName(StagingDirectory); return(iframeSource); }
/// <summary> /// Given a book, typically one in a temporary folder made just for exporting (or testing), /// and given the set of fonts found while creating that book and removing hidden elements, /// find the files needed for those fonts. /// Copy the font file for the normal style of that font family from the system font folder, /// if permitted; or post a warning in progress if we can't embed it. /// Create an extra css file (fonts.css) which tells the book to find the font files for those font families /// in the local folder, and insert a link to it into the book. /// </summary> /// <param name="fontFileFinder">use new FontFinder() for real, or a stub in testing</param> public static void EmbedFonts(Book.Book book, IWebSocketProgress progress, HashSet <string> fontsWanted, IFontFinder fontFileFinder) { const string defaultFont = "Andika New Basic"; // already in BR, don't need to embed or make rule. fontsWanted.Remove(defaultFont); PublishHelper.CheckFontsForEmbedding(progress, fontsWanted, fontFileFinder, out List <string> filesToEmbed, out HashSet <string> badFonts); foreach (var file in filesToEmbed) { // Enhance: do we need to worry about problem characters in font file names? var dest = Path.Combine(book.FolderPath, Path.GetFileName(file)); RobustFile.Copy(file, dest); } // Create the fonts.css file, which tells the browser where to find the fonts for those families. var sb = new StringBuilder(); foreach (var font in fontsWanted) { if (badFonts.Contains(font)) { continue; } var group = fontFileFinder.GetGroupForFont(font); if (group != null) { EpubMaker.AddFontFace(sb, font, "normal", "normal", group.Normal); } // We don't need (or want) a rule to use Andika instead. // The reader typically WILL use Andika, because we have a rule making it the default font // for the whole body of the document, and BloomReader always has it available. // However, it's possible that although we aren't allowed to embed the desired font, // the device actually has it installed. In that case, we want to use it. } RobustFile.WriteAllText(Path.Combine(book.FolderPath, "fonts.css"), sb.ToString()); // Tell the document to use the new stylesheet. book.OurHtmlDom.AddStyleSheet("fonts.css"); // Repair defaultLangStyles.css and other places in the output book if needed. if (badFonts.Any()) { PublishHelper.FixCssReferencesForBadFonts(book.FolderPath, defaultFont, badFonts); PublishHelper.FixXmlDomReferencesForBadFonts(book.OurHtmlDom.RawDom, defaultFont, badFonts); } }
public void Dispose() { if (RobustFile.Exists(PdfFilePath)) { try { RobustFile.Delete(PdfFilePath); } catch (Exception) { } } if (EpubMaker != null) { EpubMaker.Dispose(); EpubMaker = null; } GC.SuppressFinalize(this); }
internal void SaveAsEpub() { using (var dlg = new SaveFileDialog()) { if (!string.IsNullOrEmpty(_lastDirectory) && Directory.Exists(_lastDirectory)) { dlg.InitialDirectory = _lastDirectory; } string suggestedName = string.Format("{0}-{1}.epub", Path.GetFileName(BookSelection.CurrentSelection.FolderPath), _collectionSettings.GetLanguage1Name("en")); dlg.FileName = suggestedName; dlg.Filter = "EPUB|*.epub"; if (DialogResult.OK == dlg.ShowDialog()) { _lastDirectory = Path.GetDirectoryName(dlg.FileName); EpubMaker.FinishEpub(dlg.FileName); ReportAnalytics("Save ePUB"); } } }
/// <summary> /// Given a book, typically one in a temporary folder made just for exporting (or testing), /// examine the CSS files and determine what fonts should be necessary. (Enhance: we could actually /// load the book into a DOM and find out what font IS used for each block.) /// Copy the font file for the normal style of that font family from the system font folder, /// if permitted; or post a warning in progress if we can't embed it. /// Create an extra css file (fonts.css) which tells the book to find the font files for those font families /// in the local folder, and insert a link to it into the book. /// </summary> /// <param name="book"></param> /// <param name="progress"></param> /// <param name="fontFileFinder">use new FontFinder() for real, or a stub in testing</param> public static void EmbedFonts(Book book, IWebSocketProgress progress, IFontFinder fontFileFinder) { const string defaultFont = "Andika New Basic"; // already in BR, don't need to embed or make rule. // The 'false' here says to ignore all but the first font face in CSS's ordered lists of desired font faces. // If someone is publishing an Epub, they should have that font showing. For one thing, this makes it easier // for us to not embed fonts we don't want/ need.For another, it makes it less likely that an epub will look // different or have glyph errors when shown on a machine that does have that primary font. var fontsWanted = EpubMaker.GetFontsUsed(book.FolderPath, false).ToList(); fontsWanted.Remove(defaultFont); fontFileFinder.NoteFontsWeCantInstall = true; var filesToEmbed = new List <string>(); foreach (var font in fontsWanted) { var fontFiles = fontFileFinder.GetFilesForFont(font); if (fontFiles.Count() > 0) { filesToEmbed.AddRange(fontFiles); progress.MessageWithParams("CheckFontOK", "{0} is a font name", "Checking {0} font: License OK for embedding.", font); // Assumes only one font file per font; if we embed multiple ones will need to enhance this. var size = new FileInfo(fontFiles.First()).Length; var sizeToReport = (size / 1000000.0).ToString("F1"); // purposely locale-specific; might be e.g. 1,2 progress.MessageWithColorAndParams("Embedding", "{1} is a number with one decimal place, the number of megabytes the font file takes up", "blue", "Embedding font {0} at a cost of {1} megs", font, sizeToReport); continue; } if (fontFileFinder.FontsWeCantInstall.Contains(font)) { progress.ErrorWithParams("LicenseForbids", "{0} is a font name", "Checking {0} font: License does not permit embedding.", font); } else { progress.ErrorWithParams("NoFontFound", "{0} is a font name", "Checking {0} font: No font found to embed.", font); } progress.ErrorWithParams("SubstitutingAndika", "{0} and {1} are font names", "Substituting \"{0}\" for \"{1}\"", defaultFont, font); } foreach (var file in filesToEmbed) { // Enhance: do we need to worry about problem characters in font file names? var dest = Path.Combine(book.FolderPath, Path.GetFileName(file)); RobustFile.Copy(file, dest); } // Create the fonts.css file, which tells the browser where to find the fonts for those families. var sb = new StringBuilder(); foreach (var font in fontsWanted) { var group = fontFileFinder.GetGroupForFont(font); if (group != null) { EpubMaker.AddFontFace(sb, font, "normal", "normal", group.Normal); } // We don't need (or want) a rule to use Andika instead. // The reader typically WILL use Andika, because we have a rule making it the default font // for the whole body of the document, and BloomReader always has it available. // However, it's possible that although we aren't allowed to embed the desired font, // the device actually has it installed. In that case, we want to use it. } RobustFile.WriteAllText(Path.Combine(book.FolderPath, "fonts.css"), sb.ToString()); // Tell the document to use the new stylesheet. book.OurHtmlDom.AddStyleSheet("fonts.css"); }
private void RemoveUnwantedContentInternal(HtmlDom dom, Book.Book book, bool removeInactiveLanguages, EpubMaker epubMaker, ISet <string> warningMessages, bool keepPageLabels = false) { // The ControlForInvoke can be null for tests. If it's not null, we better not need an Invoke! Debug.Assert(ControlForInvoke == null || !ControlForInvoke.InvokeRequired); // should be called on UI thread. Debug.Assert(dom != null && dom.Body != null); // Collect all the page divs. var pageElts = new List <XmlElement>(); if (epubMaker != null) { pageElts.Add((XmlElement)dom.Body.FirstChild); // already have a single-page dom prepared for export } else { foreach (XmlElement page in book.GetPageElements()) { pageElts.Add(page); } } RemoveEnterpriseFeaturesIfNeeded(book, pageElts, warningMessages); // Remove any left-over bubbles foreach (XmlElement elt in dom.RawDom.SafeSelectNodes("//label")) { if (HasClass(elt, "bubble")) { elt.ParentNode.RemoveChild(elt); } } // Remove page labels and descriptions. Also remove pages (or other div elements) that users have // marked invisible. (The last mimics the effect of bookLayout/languageDisplay.less for editing // or PDF published books.) foreach (XmlElement elt in dom.RawDom.SafeSelectNodes("//div")) { if (!book.IsTemplateBook) { if (!keepPageLabels && HasClass(elt, "pageLabel")) { elt.ParentNode.RemoveChild(elt); } if (HasClass(elt, "pageDescription")) { elt.ParentNode.RemoveChild(elt); } } // REVIEW: is this needed now with the new strategy? if (HasClass(elt, "bloom-editable") && HasClass(elt, "bloom-visibility-user-off")) { elt.ParentNode.RemoveChild(elt); } } // Our recordingmd5 attribute is not allowed by epub foreach (XmlElement elt in HtmlDom.SelectAudioSentenceElementsWithRecordingMd5(dom.RawDom.DocumentElement)) { elt.RemoveAttribute("recordingmd5"); } // Users should not be able to edit content of published books foreach (XmlElement elt in dom.RawDom.SafeSelectNodes("//div[@contenteditable]")) { elt.RemoveAttribute("contenteditable"); } foreach (var div in dom.Body.SelectNodes("//div[@role='textbox']").Cast <XmlElement>()) { div.RemoveAttribute("role"); // this isn't an editable textbox in an ebook div.RemoveAttribute("aria-label"); // don't want this without a role div.RemoveAttribute("spellcheck"); // too late for spell checking in an ebook div.RemoveAttribute("content-editable"); // too late for editing in an ebook } // Clean up img elements (BL-6035/BL-6036 and BL-7218) foreach (var img in dom.Body.SelectNodes("//img").Cast <XmlElement>()) { // Ensuring a proper alt attribute is handled elsewhere var src = img.GetOptionalStringAttribute("src", null); if (String.IsNullOrEmpty(src) || src == "placeHolder.png") { // If this is a template book, then the whole point of the book is to not have content. So then we want to preserve the placeholders so // that people looking at the book on Bloom Library can see how the template pages are constructed. if (!book.IsTemplateBook) { // If the image file doesn't exist, we want to find out about it. But if there is no // image file, epubcheck complains and it doesn't do any good anyway. img.ParentNode.RemoveChild(img); } } else { var parent = img.ParentNode as XmlElement; parent.RemoveAttribute("title"); // We don't want this in published books. img.RemoveAttribute( "title"); // We don't want this in published books. (probably doesn't exist) img.RemoveAttribute("type"); // This is invalid, but has appeared for svg branding images. } } if (epubMaker != null) { // epub-check doesn't like these attributes (BL-6036). I suppose BloomReader might find them useful. foreach (var div in dom.Body.SelectNodes("//div[contains(@class, 'split-pane-component-inner')]").Cast <XmlElement>()) { div.RemoveAttribute("min-height"); div.RemoveAttribute("min-width"); } } // These elements are inserted and supposedly removed by the ckeditor javascript code. // But at least one book created by our test team still has one output to an epub. If it // exists, it probably has a style attribute (position:fixed) that epubcheck won't like. // (fixed position way off the screen to hide it) foreach (var div in dom.Body.SelectNodes("//*[@data-cke-hidden-sel]").Cast <XmlElement>()) { div.ParentNode.RemoveChild(div); } // Finally we try to remove elements (except image descriptions) that aren't visible. // To accurately determine visibility, we point a real browser at the document. // We've had some problems with this, which we now think are fixed; if it doesn't work, for // BloomReader we just allow the document to be a little bigger than it needs to be. // BloomReader will obey rules like display:none. // For epubs, we don't; display:none is not reliably obeyed, so the reader could see // unexpected things. HtmlDom displayDom = null; foreach (XmlElement page in pageElts) { EnsureAllThingsThatCanBeHiddenHaveIds(page); if (displayDom == null) { displayDom = book.GetHtmlDomWithJustOnePage(page); } else { var pageNode = displayDom.RawDom.ImportNode(page, true); displayDom.Body.AppendChild(pageNode); } } if (displayDom == null) { return; } if (epubMaker != null) { epubMaker.AddEpubVisibilityStylesheetAndClass(displayDom); } if (this != _latestInstance) { return; } if (!_browser.NavigateAndWaitTillDone(displayDom, 10000, "publish", () => this != _latestInstance, false)) { // We started having problems with timeouts here (BL-7892). // We may as well carry on. We only need the browser to have navigated so calls to IsDisplayed(elt) // below will give accurate answers. Even if the browser hasn't gotten that far yet (e.g., in // a long document), it may stay ahead of us. We'll report a failure (currently only for epubs, see above) // if we actually can't find the element we need in IsDisplayed(). Debug.WriteLine("Failed to navigate fully to RemoveUnwantedContentInternal DOM"); Logger.WriteEvent("Failed to navigate fully to RemoveUnwantedContentInternal DOM"); } if (this != _latestInstance) { return; } var toBeDeleted = new List <XmlElement>(); // Deleting the elements in place during the foreach messes up the list and some things that should be deleted aren't // (See BL-5234). So we gather up the elements to be deleted and delete them afterwards. foreach (XmlElement page in pageElts) { // BL-9501 Don't remove pages from template books, which are often empty but we still want to show their components if (!book.IsTemplateBook) { // As the constant's name here suggests, in theory, we could include divs // that don't have .bloom-editable, and all their children. // But I'm not smart enough to write that selector and for bloomds, all we're doing here is saving space, // so those other divs we are missing doesn't seem to matter as far as I can think. var kSelectThingsThatCanBeHiddenButAreNotText = ".//img"; var selector = removeInactiveLanguages ? kSelectThingsThatCanBeHidden : kSelectThingsThatCanBeHiddenButAreNotText; foreach (XmlElement elt in page.SafeSelectNodes(selector)) { // Even when they are not displayed we want to keep image descriptions if they aren't empty. // This is necessary for retaining any associated audio files to play. // (If they are empty, they won't have any audio and may trigger embedding an unneeded font.) // See https://issues.bloomlibrary.org/youtrack/issue/BL-7237. // As noted above, if the displayDom is not sufficiently loaded for a definitive // answer to IsDisplayed, we will throw when making epubs but not for bloom reader. if (!IsDisplayed(elt, epubMaker != null) && !IsNonEmptyImageDescription(elt)) { toBeDeleted.Add(elt); } } foreach (var elt in toBeDeleted) { elt.ParentNode.RemoveChild(elt); } } // We need the font information for wanted text elements as well. This is a side-effect but related to // unwanted elements in that we don't need fonts that are used only by unwanted elements. Note that // elements don't need to be actually visible to provide computed style information such as font-family. foreach (XmlElement elt in page.SafeSelectNodes(".//div")) { StoreFontUsed(elt); } RemoveTempIds(page); // don't need temporary IDs any more. toBeDeleted.Clear(); } }
/// <summary> /// Remove unwanted content from the XHTML of this book. As a side-effect, store the fonts used in the remaining /// content of the book. /// </summary> public void RemoveUnwantedContent(HtmlDom dom, Book.Book book, bool removeInactiveLanguages, ISet <string> warningMessages, EpubMaker epubMaker = null, bool keepPageLabels = false) { FontsUsed.Clear(); // Removing unwanted content involves a real browser really navigating. I'm not sure exactly why, // but things freeze up if we don't do it on the UI thread. if (ControlForInvoke != null) { // Linux/Mono can choose a toast as the ActiveForm. When it closes, bad things can happen // trying to use it to Invoke. if (ControlForInvoke.IsDisposed) { ControlForInvoke = Form.ActiveForm; } ControlForInvoke.Invoke((Action)(delegate { RemoveUnwantedContentInternal(dom, book, removeInactiveLanguages, epubMaker, warningMessages, keepPageLabels); })); } else { RemoveUnwantedContentInternal(dom, book, removeInactiveLanguages, epubMaker, warningMessages, keepPageLabels); } }
/// <summary> /// Given a book, typically one in a temporary folder made just for exporting (or testing), /// and given the set of fonts found while creating that book and removing hidden elements, /// find the files needed for those fonts. /// Copy the font file for the normal style of that font family from the system font folder, /// if permitted; or post a warning in progress if we can't embed it. /// Create an extra css file (fonts.css) which tells the book to find the font files for those font families /// in the local folder, and insert a link to it into the book. /// </summary> /// <param name="book"></param> /// <param name="progress"></param> /// <param name="fontFileFinder">use new FontFinder() for real, or a stub in testing</param> public static void EmbedFonts(Book.Book book, WebSocketProgress progress, HashSet <string> fontsWanted, IFontFinder fontFileFinder) { const string defaultFont = "Andika New Basic"; // already in BR, don't need to embed or make rule. fontsWanted.Remove(defaultFont); fontFileFinder.NoteFontsWeCantInstall = true; var filesToEmbed = new List <string>(); foreach (var font in fontsWanted) { var fontFiles = fontFileFinder.GetFilesForFont(font); if (fontFiles.Count() > 0) { filesToEmbed.AddRange(fontFiles); progress.MessageWithParams("PublishTab.Android.File.Progress.CheckFontOK", "{0} is a font name", "Checking {0} font: License OK for embedding.", ProgressKind.Progress, font); // Assumes only one font file per font; if we embed multiple ones will need to enhance this. var size = new FileInfo(fontFiles.First()).Length; var sizeToReport = (size / 1000000.0).ToString("F1"); // purposely locale-specific; might be e.g. 1,2 progress.MessageWithParams("PublishTab.Android.File.Progress.Embedding", "{1} is a number with one decimal place, the number of megabytes the font file takes up", "Embedding font {0} at a cost of {1} megs", ProgressKind.Note, font, sizeToReport); continue; } if (fontFileFinder.FontsWeCantInstall.Contains(font)) { //progress.Error("Common.Warning", "Warning"); progress.MessageWithParams("LicenseForbids", "{0} is a font name", "This book has text in a font named \"{0}\". The license for \"{0}\" does not permit Bloom to embed the font in the book.", ProgressKind.Error, font); } else { progress.MessageWithParams("NoFontFound", "{0} is a font name", "This book has text in a font named \"{0}\", but Bloom could not find that font on this computer.", ProgressKind.Error, font); } progress.MessageWithParams("SubstitutingAndika", "{0} is a font name", "Bloom will substitute \"{0}\" instead.", ProgressKind.Error, defaultFont, font); } foreach (var file in filesToEmbed) { // Enhance: do we need to worry about problem characters in font file names? var dest = Path.Combine(book.FolderPath, Path.GetFileName(file)); RobustFile.Copy(file, dest); } // Create the fonts.css file, which tells the browser where to find the fonts for those families. var sb = new StringBuilder(); foreach (var font in fontsWanted) { var group = fontFileFinder.GetGroupForFont(font); if (group != null) { EpubMaker.AddFontFace(sb, font, "normal", "normal", group.Normal); } // We don't need (or want) a rule to use Andika instead. // The reader typically WILL use Andika, because we have a rule making it the default font // for the whole body of the document, and BloomReader always has it available. // However, it's possible that although we aren't allowed to embed the desired font, // the device actually has it installed. In that case, we want to use it. } RobustFile.WriteAllText(Path.Combine(book.FolderPath, "fonts.css"), sb.ToString()); // Tell the document to use the new stylesheet. book.OurHtmlDom.AddStyleSheet("fonts.css"); }