Create a CSS Stylesheet for HTML Output
Mục tiêu: Create a new style.css file in the project’s root directory. This file will be used to style the HTML documents generated by the markdown parser.
Congratulations on building a comprehensive and robust test suite! You have successfully created a powerful safety net for your parser, ensuring that its logic is correct and well-protected against future regressions. This is a hallmark of professional software development. With the confidence that your core functionality is secure, you can now shift your focus from the internal logic to the external appearance of your tool’s output.
From Functional to Beautiful: Introducing CSS
Your tool currently produces HTML that is structurally perfect but visually plain. When opened in a browser, it renders with the default “user agent” styles—typically black Times New Roman text on a white background. To elevate your project from a simple utility to a polished application, you need to add styling.
The language of the web for styling is CSS (Cascading Style Sheets). CSS is a stylesheet language used to describe the presentation of a document written in a markup language like HTML. It controls everything from colors and fonts to layout and spacing.
Our goal in this step is to embed a default set of CSS rules directly into the <head> of the generated HTML document. This will make every document your tool produces look clean and modern out of the box, without requiring any external files. The very first step on this path is to create the CSS file that will contain our styles.
Creating the Stylesheet
This task does not involve writing any Rust code. Instead, you will create a new file in your project’s file system. It is crucial to place this file in the correct location: the root directory of your project. This is the main folder that contains your Cargo.toml file and the src directory.
You can create this file using your code editor’s file explorer or by running the following command in your terminal from your project’s root directory:
# On macOS or Linux
touch style.css
# On Windows (Command Prompt)
echo. > style.css
# On Windows (PowerShell)
New-Item -ItemType File -Name style.css
After creating the file, your project’s directory structure should look like this. Note that style.css is a sibling of Cargo.toml and src, not inside src.
markdown_converter/
├── .git/
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── src/
│ ├── main.rs
│ └── parser.rs
├── style.css <-- Your new file
└── target/
By placing style.css in the root, you are treating it as a project-level asset. This is a clean way to organize non-Rust files that are part of the application’s build process. In a later task, we will use a special Rust macro to read the contents of this file at compile time and embed it directly into your program’s binary.
You have now successfully set up the file that will define the visual identity of your generated documents.
Next Steps
With the empty style.css file in place, the next task is to populate it with some basic CSS rules. You will add styles for common HTML elements like the body, headings (h1, h2), paragraphs (p), and bold text (strong) to create a clean, readable, and modern design for your output.
Further Reading
To understand the fundamentals of CSS and why it’s so powerful, I highly recommend exploring the MDN Web Docs.
- CSS basics: An excellent, beginner-friendly introduction to the core concepts of Cascading Style Sheets.
- Project Layout: The official Cargo Book discusses conventions for laying out a Rust project.
Implement a Modern Default Stylesheet
Mục tiêu: Populate the style.css file with a set of clean, modern, and readable CSS rules to provide a professional default style for common HTML elements like headings, paragraphs, and links.
Excellent work on the previous task! You’ve successfully created the style.css file and placed it in your project’s root directory. This empty file is like a blank canvas, ready for you to paint the visual identity of your generated HTML documents.
Now, we will breathe life into this file by adding a set of CSS rules. Our goal is not to create a complex, flashy design, but rather a clean, modern, and highly readable stylesheet that serves as a professional default. This will instantly make your converter’s output look polished and well-crafted.
The Anatomy of a CSS Rule
Before we write the code, let’s quickly review the structure of a CSS rule. It consists of two main parts:
- Selector: This targets the HTML element(s) you want to style. For example,
h1selects all<h1>elements, andpselects all<p>elements. - Declaration Block: This is enclosed in curly braces
{}and contains one or more declarations. Each declaration is aproperty: value;pair (e.g.,color: blue;) that applies a specific style to the selected element.
We will create rules for the most common elements your parser generates: the main <body>, headings (h1, h2), paragraphs (p), links (a), and bold/italic text.
Open your empty style.css file and add the following content.
/* A clean, modern, and readable default stylesheet. */
/* 1. Global Styles & Typography */
body {
/* Use a modern, sans-serif font stack that prioritizes system fonts. */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
/* Set a comfortable line height for better readability. 1.6 is unitless, meaning 1.6 times the font size. */
line-height: 1.6;
/* Set the base text color to a dark gray for better contrast than pure black. */
color: #333;
/* Center the content in a column that's easy to read on wide screens. */
max-width: 800px; /* The content will not grow wider than 800px. */
margin: 0 auto; /* "0" for top/bottom margin, "auto" for left/right makes it center. */
/* Add some padding so text doesn't touch the browser edges. */
padding: 2rem;
/* A slightly off-white background color can be easier on the eyes. */
background-color: #f9f9f9;
}
/* 2. Headings */
h1, h2 {
/* Give headings a slightly bolder weight and a different color to stand out. */
font-weight: 600;
color: #111;
/* Add some space above and below headings to create a clear visual hierarchy. */
margin-top: 2.5rem;
margin-bottom: 1rem;
/* Add a subtle bottom border to the main heading. */
border-bottom: 1px solid #ddd;
padding-bottom: 0.5rem;
}
/* 3. Paragraphs */
p {
/* Ensure consistent spacing between paragraphs. */
margin-bottom: 1.2rem;
}
/* 4. Links */
a {
/* Give links a distinct color to show they are interactive. */
color: #007acc;
/* Remove the default underline for a cleaner look. */
text-decoration: none;
}
a:hover {
/* Add the underline back when the user hovers over the link, providing clear feedback. */
text-decoration: underline;
}
/* 5. Inline Elements */
strong {
/* You can make bold text even more prominent if desired. */
font-weight: 700;
color: #000;
}
em {
/* The default italic style is usually sufficient, but you can customize it. */
font-style: italic;
}
Deconstructing the Stylesheet
Let’s break down what each part of this CSS does.
1. Global Styles (body)
The body selector is the most important one, as many of its properties are inherited by the elements inside it.
font-family: This is a “font stack.” The browser tries to use the first font in the list. If it’s not available on the user’s system, it tries the next, and so on. This stack prioritizes modern system fonts, which are fast-loading and look great on their respective operating systems (e.g., “Segoe UI” on Windows,BlinkMacSystemFonton macOS).sans-serifis the final fallback.line-height: 1.6;: This controls the spacing between lines of text. A value between 1.5 and 1.7 is generally considered ideal for readability.color: #333;: We use a very dark gray instead of pure black (#000000). This slightly lower contrast can reduce eye strain for some readers.max-width: 800px;&margin: 0 auto;: This is a classic CSS technique for creating a centered content column. It prevents your text from stretching across the entire width of a very large monitor, which would make it difficult to read.padding: 2rem;: This adds 2 “rem” units of space inside the body, preventing text from sitting right against the edges of the browser window. Aremis a flexible unit based on the root font size, making it great for accessibility.
2. Headings (h1, h2)
By using a comma (h1, h2), we apply these styles to both <h1> and <h2> elements.
font-weight: 600;: This makes the headings semi-bold, helping them stand out.margin-top&margin-bottom: These properties control the empty space outside the element’s border. Adding more space around headings is key to creating a clear visual hierarchy and making the document easy to scan.border-bottom: A subtle line under the main heading provides a nice visual anchor for the top of the page.
3. Links (a and a:hover)
a: We select all<a>(anchor) tags and give them a distinct blue color to signify that they are clickable. We also remove the default underline for a cleaner look.a:hover: This is a pseudo-class. It applies styles only when the user’s mouse cursor is hovering over the link. Re-introducing the underline on hover is a standard user experience pattern that provides clear interactive feedback.
You have now created a complete, professional-looking stylesheet. Save the style.css file. Your application isn’t using it yet, but the asset itself is now ready.
Next Steps
With your beautiful CSS rules written and saved in style.css, the next logical step is to bring this content into your Rust application. You will learn how to use the powerful include_str! macro. This special macro will read the entire content of your style.css file at compile time and embed it directly into your program’s binary as a string constant, making your CLI tool a self-contained executable with no external dependencies.
Further Reading
To dive deeper into the CSS properties you’ve just used and to learn more fundamental concepts, these MDN Web Docs are invaluable.
- Introduction to CSS: A great starting point if you want to learn more about the language.
- The CSS Box Model: A fundamental concept that explains how margins, padding, and borders work together.
- Fundamental CSS Selectors: Learn more ways to target elements, such as by class, ID, or attribute.
- Web Typography: A deep dive into styling text on the web.
Embed CSS into Rust Binary using include\_str!
Mục tiêu: Learn how to make your Rust application a self-contained binary by embedding an external CSS file directly into the executable at compile time using the powerful include\_str! macro.
You have done a fantastic job creating a beautiful and modern stylesheet in style.css. That file is now a self-contained set of design rules, ready to be applied to your generated documents. The canvas has been painted; the next challenge is to frame it and hang it inside your HTML.
This brings us to a crucial question: how do we get the contents of an external file like style.css into our Rust program? One approach might be to read the file at runtime, similar to how you read the input Markdown file. However, this would mean that for your tool to work, the style.css file would always need to be distributed alongside your final executable. This creates a dependency and makes your application less portable.
Fortunately, Rust provides a far more elegant and robust solution: compile-time file inclusion.
Baking Assets into Your Binary with include_str!
Rust has a powerful macro system, and one of the most useful built-in macros for a project like this is include_str!. Here’s what it does:
- It runs at compile time: When you run
cargo buildorcargo run, the Rust compiler executes theinclude_str!macro. - It reads a file: The macro reads the entire content of the file specified in its path.
- It becomes a string literal: The macro then replaces itself in the code with a static string literal (
&'static str) containing the file’s contents.
The result is that the CSS code from your style.css file is “baked” directly into your final compiled executable. Your CLI tool becomes a single, self-contained binary with zero external file dependencies, making it incredibly easy to share and distribute.
The string it produces is a &'static str. The 'static lifetime here is a promise to the compiler that this string data will be available for the entire lifetime of the program, because it’s part of the program’s binary data itself. This is the most efficient way to handle a fixed, read-only piece of text.
We will now use this macro inside your generate_html_document function in src/main.rs. We’ll store the CSS content in a const, which is a Rust constant whose value is set at compile time—a perfect match for the compile-time nature of include_str!.
Let’s modify the generate_html_document function in src/main.rs.
// In src/main.rs
// ... (use statements, mod parser) ...
fn generate_html_document(body_content: &str) -> String {
// At compile time, the `include_str!` macro will read the `style.css` file
// and embed its contents directly into our program as a static string slice.
// We store this in a `const` because its value is known at compile time
// and will never change.
// The path is relative to the current source file (`src/main.rs`), so we go
// one level up (`../`) to the project root to find `style.css`.
const CSS_STYLE: &str = include_str!("../style.css");
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Converted Document</title>
</head>
<body>
{}
</body>
</html>
"#,
body_content
)
}
// ... (main function and CliArgs struct) ...
Deconstructing the New Code
The change is small, but the concept is powerful. Let’s break down the single line you added:
const CSS_STYLE: &str: This declares a constant namedCSS_STYLE. By convention, constant names are inUPPER_SNAKE_CASE. Aconstis a value that is hardcoded into the program at compile time. It’s not a variable; it’s more like a named replacement.include_str!("../style.css"): This is the core of the implementation.- The
!signifies thatinclude_stris a macro, not a function. - The path
"../style.css"is crucial. The path given toinclude_str!is relative to the source file it is written in. Since your code is insrc/main.rs, you need to go “up” one directory level (..) to get to the project root wherestyle.cssis located. This is a common point of confusion, and getting the relative path right is key.
- The
With this single line, you have successfully loaded your entire stylesheet into a compile-time constant within your Rust program. The CSS_STYLE constant now holds the complete text of your CSS file, ready to be used. Your application is now self-contained. Even if you were to delete the style.css file after compiling, the program would still work perfectly because the styles are now a permanent part of the executable.
Next Steps
You’ve successfully brought the CSS content into your Rust application. It’s now available in the CSS_STYLE constant. However, it’s not yet being used in the generated HTML. In the next task, you will modify the format! macro within the generate_html_document function to inject this CSS content inside a <style> tag, placing it directly into the <head> of the final document.
Further Reading
To deepen your understanding of the powerful concepts you’ve just used, I highly recommend these resources:
- The
include_str!Macro: The official Rust documentation for this macro. - Macros in The Rust Book: An introduction to the concept of macros in Rust.
- Constants in Rust: A detailed look at
constitems in the Rust language. constversusstatic: You might also seestaticused for global values. This article explains the subtle but important differences betweenconstandstatic.
Embed CSS into Generated HTML
*Mục tiêu: Update a Rust function to inject a compile-time CSS string into an HTML template using the tag and the format! macro to create a self-contained, styled document.</em></p>
Excellent! You've successfully used the powerful include\_str! macro to load your entire stylesheet into the CSS\_STYLE constant at compile time. Your Rust application is now a self-contained unit, with the design rules for your document baked directly into the executable.
However, right now, that constant is just sitting inside the function, unused. The CSS content has been brought into the workshop, but it hasn't been applied to the final product. This final task is where we connect the wires and inject these styles directly into the generated HTML.
Embedding CSS with the <style> Tag
The standard HTML mechanism for embedding a block of CSS directly into a document is the <style> tag. Anything placed between <style> and </style> is interpreted by the browser not as content to be displayed, but as CSS rules to be applied to the document. This tag must be placed inside the <head> section of the HTML.
Our goal is to modify our HTML template string to include these <style> tags and place our CSS\_STYLE constant inside them. To do this, we will add a second placeholder {} to our format! macro's template and pass CSS\_STYLE as the first argument.
Let's update the generate\_html\_document function in src/main.rs to complete the integration.
// In src/main.rs
// ... (use statements, mod parser) ...
fn generate\_html\_document(body\_content: &str) -> String {
// The CSS\_STYLE constant is ready to be used.
const CSS\_STYLE: &str = include\_str!("../style.css");
// We now have two `{}` placeholders in our template string.
// `format!` will replace them with the arguments that follow, in order.
// The first `{}` will be replaced by CSS\_STYLE.
// The second `{}` will be replaced by body\_content.
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Converted Document</title>
<style>
{}
</style>
</head>
<body>
{}
</body>
</html>
"#,
CSS\_STYLE, // The first argument for the first placeholder.
body\_content // The second argument for the second placeholder.
)
}
// ... (main function and CliArgs struct) ...
Deconstructing the Final Change
Let's break down the elegant but powerful update you've just made to the format! macro call.
-
<style>and</style>Tags: You've added these tags within the<head>section of your HTML template. This is the correct, standards-compliant location for embedded stylesheets. It ensures the browser loads and parses the styling rules before it starts rendering the content in the<body>, which prevents a "flash of unstyled content." -
A Second Placeholder (
{}): Your template string now contains two{}placeholders. Theformat!macro is designed to handle any number of placeholders. It processes them sequentially. -
Multiple Arguments to
format!: The arguments you pass toformat!after the template string are mapped one-to-one with the placeholders.- The first placeholder is filled by the first argument:
CSS\_STYLE. - The second placeholder is filled by the second argument:
body\_content. The order is critical. Swapping the arguments would incorrectly place the HTML body content inside the<style>tag and the CSS into the<body>!
- The first placeholder is filled by the first argument:
You have now fully completed the styling pipeline. Your generate\_html\_document function now orchestrates a complete assembly: it takes the HTML body, fetches the pre-compiled CSS, and masterfully weaves them together into a single, styled, and complete HTML5 document.
The Moment of Truth: See Your Styles in Action
This is the final task in this step, and the reward is a visual one. It's time to see all your hard work pay off.
Prepare a sample.md file if you don't have one already:
# My Styled Document
This is a paragraph with some \*\*bold\*\* and \*italic\* text.
## A Subheading
Links should look nice, too. Here's one to the [Rust website](https://www.rust-lang.org).
Now, run your program from the terminal:
cargo run -- sample.md styled\_output.html
Find the newly created styled\_output.html file in your project directory and open it with your web browser.
Instead of the plain, black-and-white text, you will be greeted by a beautifully rendered page. You'll see the centered content column, the modern sans-serif font, the styled headings, the custom link color, and all the other professional touches you defined in your CSS. You have successfully transformed your tool from a simple converter into a polished document generator.
Next Steps
Congratulations! You have successfully integrated a default stylesheet, making your project's output visually appealing and professional. You've learned how to handle non-Rust assets and embed them at compile time, creating a truly portable executable.
The next major step will be to add a highly-requested feature for developer tools: a watch mode. You will learn how to use the notify crate to monitor the input Markdown file for any changes. When a change is detected, your tool will automatically re-run the conversion process, providing a seamless and efficient workflow for users as they write their documents.
Further Reading
To learn more about the HTML and Rust concepts you've just mastered, these resources are excellent references.
- The
<style>element: The definitive guide from MDN Web Docs on embedding styles. - Internal, External, and Inline Styles: A great overview of the three ways to apply CSS to HTML, helping you understand why you chose the "internal" method.
- Rust's Formatted Output: For a deeper dive into all the capabilities of the
format!macro family, the standard library documentation is the ultimate resource.
</div> </div>
Bài 1.7.5 Run the Program and View the Styled Output
Run the Program and View the Styled Output
Mục tiêu: Execute your Rust Markdown converter to generate a styled HTML file. Open the output in a web browser to visually confirm that the embedded CSS is correctly applied.
Fantastic! You've successfully completed all the coding for this step. The logic is now in place: you've created a professional stylesheet, used a powerful Rust macro to embed it into your application at compile time, and skillfully injected it into the <head> of your HTML template. All the individual components are assembled and ready.
This final task is the most rewarding one of the step. It's time to step away from the code editor, run your fully-enhanced program, and witness the visual transformation of your output in a web browser. This is the moment where your tool graduates from a simple data converter to a polished document generator.
The Moment of Truth: Seeing Your Styles in Action
Let's run the entire pipeline and observe the results.
First, ensure you have a good sample Markdown file to test with. This file should contain a variety of elements so you can see how each one is styled by your new CSS. If you don't have one, create a file named sample.md with the following content:
# My First Styled Document
This is a paragraph of text. Notice the clean, modern font and the comfortable line spacing. This document was generated by a tool that not only understands Markdown but also cares about presentation.
It includes \*\*bold text for emphasis\*\* and \*italic text for nuance\*.
## Subheadings Create Hierarchy
Well-structured documents use headings to guide the reader. Your stylesheet provides clear spacing and styling for these elements, making the document easy to scan and comprehend.
Links should also be clear and interactive. Here's one to the official [Rust language website](https://www.rust-lang.org) to prove the point.
Now, navigate to the root directory of your project in your terminal and execute your program. We'll direct the output to a new file, styled\_output.html, to see the final product.
cargo run -- sample.md styled\_output.html
You should see the familiar success message in your terminal:
Successfully converted "sample.md" to "styled\_output.html".
The final, crucial step is to locate the newly created styled\_output.html file in your project directory and open it in your web browser (you can usually just double-click the file).
Deconstructing the Visual Masterpiece
Instead of the browser's plain, default styling, you should be greeted by a beautifully rendered page that directly reflects the CSS rules you wrote:
- Centered Content: The text will be in a clean, centered column with a maximum width, making it highly readable on any screen size. This is thanks to your
max-width: 800px;andmargin: 0 auto;rules. - Modern Typography: The font will be a modern, crisp sans-serif font (like Segoe UI, San Francisco, or Roboto, depending on your OS), a direct result of your
font-familystack. - Improved Readability: The spacing between lines (
line-height: 1.6;) and paragraphs (margin-bottom: 1.2rem;) will make the text feel open and easy to read. - Visual Hierarchy: Headings will be distinct and well-spaced, and the main
<h1>will have a subtle underline, clearly establishing its importance. - Interactive Links: The link to the Rust website will be a distinct color, and when you hover your mouse over it, an underline will appear, providing clear, intuitive user feedback.
If you're curious, you can use your browser's developer tools (usually by right-clicking the page and selecting "Inspect" or "Inspect Element") to look at the HTML source. Inside the <head> tag, you will see your entire CSS file neatly embedded within the <style> tags, a direct confirmation that your include\_str! and format! logic worked perfectly.
Congratulations! You have successfully transformed your tool's output from raw data into a polished, professional-looking document.
Next Steps
Your Markdown to HTML converter is now highly capable. It parses complex syntax, is protected by a robust test suite, and produces beautiful, self-contained HTML files. The next major step is to add a feature that dramatically improves the user's workflow: a watch mode.
In the next step, you will learn how to use the popular notify crate to monitor the input Markdown file for changes. When the user saves the file, your tool will automatically and instantly regenerate the HTML output. This creates a seamless, live-preview-like experience that is a hallmark of modern development tools.
Further Reading
To learn more about the tools you can use to analyze your output and to get ideas for further styling enhancements, explore these resources.
- Browser Developer Tools: Learning to use your browser's dev tools is an essential skill for any web-related development. It allows you to inspect the generated HTML, see which CSS rules are being applied to which elements, and even experiment with style changes live in the browser.
- A Complete Guide to Flexbox: For more advanced layouts than a single centered column, CSS Flexbox is the modern standard. Understanding it is a great next step in your CSS journey.
- CSS Color Palettes: Learn how to choose professional and accessible color palettes for your designs.
- Coolors.co - A great tool for generating color schemes.
*